Gestion des headers ✱ ********************* Rappel ====== La génération d’un programme (build) consiste à transformer des codes sources au format texte en programme exécutable pour une machine donnée. Ce processus repose sur deux actions : * La **compilation** transforme chaque fichier source (.cpp) en un fichier objet. * La **liaison** (link) fusionne les différents fichiers objets pour construire le programme exécutable final. La **compilation séparée** désigne le fait de compiler chaque fichier source (.cpp) séparément. Ainsi, lors de la compilation d'un fichier source (.cpp), le compilateur n'a aucune connaissance du contenu présent dans les autres fichiers sources (.cpp). Cette approche présente plusieurs avantages : * Une meilleure gestion des projets : chaque fichier est dédié à une classe / thématique particulière. * Réduction du temps de compilation par parallélisation. * Faciliter la maintenance et le débogage du code. .. note:: La compilation se déclenche pour chaque fichier source (.cpp) par pour les fichiers d'entête (.h). Organisation ============ .. code-block:: cpp // EXPORT DE FONCTION // fichier : fnt.h // fichier : fnt.cpp #pragma once #include "fnt.h" // déclaration //définition int fnt(...); int fnt(...) { ... } /////////////////////////////////////////////////////////////////////////////////// // EXPORT D'UNE CLASSE - STYLE JAVA // fichier : maclasse.h #pragma once class T { int fnt(...) { instructions } }; /////////////////////////////////////////////////////////////////////////////////// // EXPORT D'UNE CLASSE - STYLE C++ // fichier : maclasse.h // fichier : maclasse.cpp #pragma once #include "maclasse.h" // définition de la classe // définition des fonctions membres class T T::fnt(...) { { int fnt(...); instructions }; } /////////////////////////////////////////////////////////////////////////////////// // EXPORT D'UNE FONCTION TEMPLATE // fichier : templatefnt.h #pragma once template T max(T a, T b) { if (b>a) return b; else return a; } .. note:: Lorsque le compilateur lit une directive *#include*, il substitue cette ligne par le contenu du fichier header. Le compilateur traite ainsi ces lignes comme si elles appartenaient au fichier source. Si ces nouvelles lignes incluent à leur tour d'autres directives *#include* alors le mécanisme de substitution se poursuit. Usage : * Chaque fichier source **maclasse.cpp** doit inclure son fichier header : **#include \"maclasse.h\"** * Chaque fichier header doit inclure la directive *#pragma once* * Lorsqu'un fichier (.h ou .cpp) a besoin d'un élément, il inclut le fichier header associé * Normalement, aucun fichier (.h ou /.cpp) n'inclut un fichier source (.cpp) Les problèmes ============= La directive **#pragma once** désactive toute tentative de réinclusion. Cela permet d'optimiser le temps de compilation mais pas uniquement comme nous allons le voir ci-dessous : Inclusion multiple ------------------ Supposons que nous ayons un header *V2.h* inclus dans deux autres headers *collision.h* et *path.h*. Jusque là, aucun soucis. Un fichier source *eleve.cpp* inclut le header *collision.h* et le header *path.h*. En jouant le jeu des substitutions des *#include*, le contenu de *V2.h* est finalement copié-collé deux fois dans *eleve.cpp*. .. image:: double.png :align: center Que risque-t-on ? * Pour les fonctions, le fait d'avoir plusieurs déclarations dans le même fichier source ne pose aucun problème. * Pour les structures et les fonctions templates, la règle **ODR** (One Definition Rule) fera des dégâts car le compilateur va émettre une erreur de redéfinition. Heureusement pour nous, la directive *#pragma once* règle ce problème car elle va bloquer la réinclusion d'un même header et donc éviter les problèmes de redéfinition. .. warning:: Il faut écrire les directives *#pragma once* sur la première ligne de chaque fichier header. Dépendance cyclique ------------------- .. image:: cycle.png :align: center :scale: 40% Ce scénario arrive lorque * Le header *A.h* inclut le header *B.h* * Le header *B.h* inclut le header *A.h* En compilant le fichier source *A.cpp*, le compilateur va naturellement trouver l'inclusion du header *A.h* qu'il va copier-coller à l'intérieur du code source. Ensuite, en parcourant les nouvelles lignes issues du header *A.h*, le compilateur va trouver l'inclusion du header *B.h* et copier-coller son contenu dans *A.cpp*. Mais le contenu du header de *B.h* incluant le contenu de *A.h*, on ne va jamais sortir du cycle ce qui va finir par lever erreur de compilation. .. warning:: Cette situation est rare mais vous pourrez la rencontrer. Il n'y a pas de miracle cette fois, aucune directive magique à l'horizon. Il faudra revoir la conception de votre programme et l'organisation de vos fichiers pour éviter cette situation. Définir une fonction dans un header ----------------------------------- Nous l'avons déjà dit et il faut absolument l'éviter : définir une fonction dans un header. Mais pourquoi cela pose-t-il problème ? Après tout, la directive *pragma once* est là pour empêcher toute ré-inclusion et donc il ne peut y avoir de redéfinition de la même fonction durant l'étape de compilation ! Oui, c'est en partie vrai. Mais il existe une autre configuration. Prenons un exemple : supposons que par inadvertance, nous ayons malencontreusement défini une fonction *test()* dans le fichier header *test.h*. Lors de la compilation du fichier source *A.cpp*, cette fonction est définie une seule fois et donne naissance à la fonction *void test(void)* dans le fichier *A.o*, pas d'erreur de ce côté. Il en est de même lors de la compilation du fichier source *B.cpp*, une seule définition d'une fonction *test* qui donne naissance à la fonction *void test(void)* dans le fichier *B.o*. Seulement, au moment de la liaison, le linker se retrouve avec deux fonctions, une dans *A.o* et l'autre dans *B.o* avec exactement le même propotype : *void test(void)* et à ce moment là, il émet une erreur de double définition. .. image:: definheader.jpg :align: center :scale: 40% Comme les pros ============== Voici un schéma qui présente une chaîne de compilation complète. Chaque fichier source (.cpp) déclencher une compilation. On remarques que ces fichiers source incluent différents headers, mais toujours sans créer de cycle. L'étape de compilation construit des fichiers .o qui correspondent à des morceaux du programme final. Ils seront ensuite liés pour construire un exécutable. Tout projet professionnel inclut des librairies annexes : 3D, IHM, BDD,... Ces librairies ne publient pas leurs codes sources, ceci pour diverses raisons, notamment pour préserver leur propriété intellectuelle. Ainsi, chaque librairie fournit : * Des headers contenant les fonctions/classes exportées par la librairie. * Des fichiers .lib équivalents aux fichier .o (fichiers .cpp compilés) masquant le code source. Le linker dispose ainsi de toutes les informations nécessaires pour construire l'exécutable final : .. image:: compil.png :align: center :scale: 30% Quizzz ====== .. quiz:: fichierheaders :title: Les fichiers d'entête * :quiz:`{"type":"TF","answer":"F"}` L'étape de liaison se déroule avant l'étape de compilation. * :quiz:`{"type":"TF","answer":"T"}` La compilation séparée permet de compiler les fichiers source indépendamment. * :quiz:`{"type":"TF","answer":"T"}` L'étape de compilation consiste à compiler chaque fichier source. * :quiz:`{"type":"TF","answer":"F"}` On doit définir les fonctions dans les fichiers entête. * :quiz:`{"type":"TF","answer":"F"}` Le compilateur sait résoudre les dépendances cycliques. * :quiz:`{"type":"TF","answer":"T"}` La directive *#pragma once* se rencontre dans les fichiers d'entête. * :quiz:`{"type":"TF","answer":"F"}` Si l'on inclut plusieurs fois le même fichier header dans un même fichier source, cela déclenche une erreur de liaison. * :quiz:`{"type":"TF","answer":"T"}` Une librairie externe, si elle ne fournit pas de fichiers source, propose un fichier précompilé (.lib) à la place ? * :quiz:`{"type":"TF","answer":"T"}` Il est d'usage qu'un fichier source inclut son propre fichier d'entête. * :quiz:`{"type":"TF","answer":"T"}` En C++, il est possible, comme en Java, de donner les corps des fonctions membres d'une classe directement dans le header.